package com.lognsoft.btest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Properties;
import com.lognsoft.shell.*;

public class TestCaseFile
{
	private HashMap<String, TestCaseBlock> blocks = 
		new HashMap<String, TestCaseBlock>();

	private LinkedList<TestCaseBlock> current_block_stack = 
		new LinkedList<TestCaseBlock>();
	
	private LinkedList<String> current_block_stack_arg = 
		new LinkedList<String>();
	
	private TestCaseBlock current_block = null;
	private String        current_block_arg = null;
	
	private Properties GlobalEnvs    = new Properties();
	private int last_file_id = 0;
	
	public String base_path = "";	
	
	public boolean in_block()
	{
		return current_block != null;
	}
	
	public TestCaseBlock push_block (String s, String arg)
	{
		TestCaseBlock block = blocks.get(s);	
		
		if (block != null)
		{
			push_block(block, arg);
		}
		
		return block;
	}
	
	public void push_block(TestCaseBlock b, String arg)
	{
		current_block_stack.push(current_block);
		current_block_stack_arg.push(current_block_arg);
		
		current_block = b;
		current_block_arg = arg;
	}
	
	public void pop_block()
	{
		current_block = current_block_stack.pop();
		current_block_arg = current_block_stack_arg.pop();
	}
	
	private void add_block(
		String name,
		String dep, 
		int file_id, 
		String filename, 
		int lineno) throws Exception
	{
		if (in_block())
		{
			throw new Exception ("Internal Error");
		}
		
		TestCaseBlock newBlock, parentBlock, oldBlock;
		
		newBlock = new TestCaseBlock();
		
		String[] depends = dep == null || dep.length() == 0 ? null
				: dep.split(" ");
		
		newBlock.file_no = file_id;
		newBlock.block_name = name;

		/*
		 * at same TestCase file the block cannot be duplicated
		 * */
		oldBlock = blocks.get(name);
		
		if (oldBlock != null && oldBlock.file_no == newBlock.file_no) 
		{
			throw new TestCaseException(
						filename,
						lineno,
						"block(" + name + ")  redefined" );		

		}
		
		blocks.put(newBlock.block_name, newBlock);
		
		push_block(newBlock, null);
		
		/*
		 * inherited the parent block
		 * */
		if (depends != null)
		{
			for (String parentName : depends)
			{
				parentBlock = blocks.get(parentName);
				
				if (parentBlock == null)
				{
					throw new TestCaseException(
							filename,
							lineno,
							"block(" + parentName + ")  not found" );		
				}
				
				newBlock.delayExpandEnvs.putAll( parentBlock.delayExpandEnvs );
				newBlock.expandedEnvs.putAll ( parentBlock.expandedEnvs );				
				newBlock.addAll( parentBlock );
			}
		}
	}	
	
	private void add_env (String key, String value)
	{	
		if (in_block() && value.indexOf("$@") >= 0)
		{
			current_block.delayExpandEnvs.put(key,value);
		}
		else
		{
			value = calc_expression (value);				
			Properties Prop = in_block() ? current_block.expandedEnvs : GlobalEnvs;
			Prop.setProperty (key, value);
		}		
	}
	
	public Properties get_value_of(String[] keys)
	{
		Properties prop = new Properties();
		
		if (keys == null )
		{
			return prop;
		}
		
		for (String s : keys)
		{
			prop.setProperty(s, get_value_of(s));
		}
		
		return prop;
	}
	
	public String get_value_of (String key) 
	{
		StringOp.KeyValue kv = new StringOp.KeyValue(key,':', true); 
		String value = null;		
		Properties Prop = null;

		if (kv.key.charAt(0) == '$') 
		{
			/*
			 * support $@ parameter, $@ is the current working parameter
			 * */
			if (kv.key.equals("$@") 
				&& in_block() 
				&& current_block_arg != null )
			{
				value = current_block_arg;
			}
			else
			{
				/*
				 * Error Handling
				 * */
				value = "";
			}
		} 
		else if (kv.key.charAt(0) == '+')
		{
			/*
			 * TODO inner function supports
			 * */
			return "";
		}
		else
		{
			/* 
			 * */
			if (in_block()) 
			{
				Prop = current_block.expandedEnvs;
				value = Prop.getProperty(kv.key);
			}

			if (value == null) 
			{
				Prop = GlobalEnvs;
				value = Prop.getProperty(kv.key);
			}
			
			if (value == null && in_block())
			{
				value = current_block.delayExpandEnvs.getProperty(kv.key);
				
				if (value != null)
				{
					value = calc_expression (value);
				}
			}
			
			if (value == null)
			{
				value = "";
			}
		}
		
		if (kv.value.length() > 0) {
			/*
			 * $(SRC:.c=.obj) $(SRC:~10,2)
			 */

			if (kv.value.charAt(0) == '~') 
			{
				/*
				 * TODO string operation
				 */
			} 
			else 
			{
				/* String replace */
				StringOp.KeyValue repl = new StringOp.KeyValue(kv.value,'=',false);
				
				if (repl.key != null) 
				{
					value = value.replaceAll(repl.key, repl.value);
				}
			}
		}

		return value;
	}	
	
	private String calc_expression_command (String s) 
	{
		StringBuffer r;
		
		int ind, from;
		
		if (s == null)
		{
			return "";
		}

		from = s.indexOf('`');

		if (from < 0) 
		{
			return s;
		}

		r = new StringBuffer();

		if (from > 0) 
		{
			r.append(s.substring(0, from));
		}

		while (from < s.length()) 
		{
			ind = s.indexOf('`', from);

			if (ind < 0) 
			{
				r.append(s.substring(from));
				break;
			}

			if (from < ind) 
			{
				r.append(s.substring(from, ind));
			}

			from = ind + 1;
			ind = s.indexOf('`', from);

			if (ind < 0) 
			{
				break;
			}

			String key = s.substring(from, ind);
			from = ind + 1;
			
			ShellExecute sh = new ShellExecute();
			r.append( sh.run_stringOut(base_path, key, false));
		};

		s = r.toString();
		
		return s == null ? "" : s;
	}

	public String calc_expression(String s) 
	{
		StringBuffer r;
		int ind, from;

		/* fast detect */
		from = s.indexOf('$');

		if (from < 0)
		{
			return calc_expression_command(s);
		}

		r = new StringBuffer();

		if (from > 0) 
		{
			r.append(s.substring(0, from));
		}

		/* parser one by one */
		/*
		 *   $(+DATE)
		 *   $@
		 *	 $(OS.SYSTEM:Linux=Unix)
		 * */
		while (from < s.length()) 
		{
			ind = s.indexOf('$', from);

			if (ind < 0) 
			{
				r.append(s.substring(from));
				break;
			}

			if (from < ind) 
			{
				r.append(s.substring(from, ind));
			}

			from = ind + 1;
			if (from >= s.length()) 
			{
				break;
			}

			String key;
			char varFlag = s.charAt(from++);

			if (varFlag == '(') 
			{
				ind = s.indexOf(')', from);
				if (ind < 0) 
				{
					break;
				}
				else 
				{
					key = s.substring(from, ind);
					from = ind + 1;
				}
			}
			else if (varFlag == '@') 
			{
				key = "$@";
			}
			else
			{
				r.append("$" + varFlag);
				continue;
			}

			r.append(get_value_of(key));
		};
		
		return calc_expression_command(r.toString());
	};
	
	public String[] getTestCases(String arg0)
	{
		String val = get_value_of(TestCaseDef.CasesKey);
		String[] Cases = val == null || val.length() == 0 
				? null 
				: val.split(" ");
		return Cases;		
	}
	
	public void load(String name) throws Exception
	{
		if (name.startsWith("res://"))
		{
			load ("res://", name.substring(6));
		}
		else
		{
			StringOp.KeyValue fn = FileOp.path_cat ( FileOp.current_path(), name, true);
		
			if (fn == null)
			{
				throw new TestCaseException ("Error: " + name +" not found" );
			}
		
			load (fn.key, fn.value);
		}
	}
	
	public void load(String path, String name) throws Exception 
	{
		base_path = path;	
		load_internal(path, name, ++last_file_id);
	};
	
	private static char line_type(String s)
	{
		if (s.charAt(0) == '*')
		{
			return '*';
		}
		
		for (int i = 0 ; i <s.length(); i++)
		{
			char ch = s.charAt(i);
			
			if (ch == '=') return '=';
			else if (ch == ':') return ':';
		}
		
		return '\0';
	}
	
	private void load_internal(String path, String name, int file_id)
			throws Exception 
	{
		ArrayList<String> lines;
		String key,s;
		int ind;
		boolean have_space = false;

		try
		{
			lines = FileOp.getLinesFromFile(path + name);
		} 
		catch (Exception e) 
		{
			lines = null;
		}

		if (lines == null) 
		{
			throw new TestCaseException ( FileOp.relative_path(path + name) + " load failed");
		}
		
		for (int file_lineno = 0; file_lineno < lines.size();)
		{
			s = lines.get( file_lineno++ );
			
			/*
			 * multiple lines
			 */			
			while ( s.endsWith("\\") && file_lineno < lines.size() )
			{
				s = s.substring(0, s.length() - 1 ) + " " + lines.get(file_lineno++);
			}

			if (s.length() == 0)
			{
				continue;
			}
			
			have_space = s.charAt(0) == ' ' || s.charAt(0) == '\t';		

			s = s.replace('\t', ' ').replace('\r', ' ').trim();

			/*
			 * empty line or comments
			 */
			if (s.length() == 0 || s.startsWith("--") || s.startsWith("//")) 
			{
				continue;
			}
			
			/* pre compile
			 * TODO
			 * */
			
			/* analysis include */
			if ( in_block() == false && s.startsWith("include ")) 
			{
				String FileName = calc_expression(s.substring(8).trim());
				FileName = StringOp.RemoveBracket(FileName,"\"\'<{[", "\"\'>}]");
				
				if (FileName == null || FileName.length() == 0) 
				{
					throw new TestCaseException ( 
							path + name, 
							file_lineno, 
							"include missing filename" );
				}

				StringOp.KeyValue fn = FileOp.path_cat(path, FileName, true);

				if (fn == null) 
				{
					throw new TestCaseException (
							path + name,
							file_lineno, 
							FileName + " not found");
				}

				load_internal(fn.key, fn.value, ++last_file_id);
				
				continue;
			}
			
			/*
			 * exit the block;
			 */
			if (in_block() && false == have_space) 
			{
				pop_block();
			}
			
			char ty = line_type(s);

			/* analysis block begin */
			if (false == have_space && ty == ':')
			{
				ind = s.indexOf(':'); 
				String block_name = s.substring(0, ind).trim();
				String block_dep = calc_expression ( s.substring(ind + 1).trim() );
				
				add_block(block_name, block_dep, last_file_id, path + name, file_lineno);
				
				continue;
			}
			
			if (ty =='=')
			{
				ind = s.indexOf('=');
				key = s.substring(0,ind).trim();
				add_env ( key, s.substring(ind + 1).trim());				
				continue;
			};

			if ( in_block() == false )
			{
				throw new TestCaseException (
						path + name,
						file_lineno,
						"unknown line");				
			}
			
			if (ty == '*')
			{
				/* command line */
				current_block.add (s.substring(1).trim());
			}
			else
			{
				throw new TestCaseException (
						path + name,
						file_lineno,
						"unknown line format");				
			}
		}
	}
}
